原理分析
参考:https://www.cnblogs.com/huangying2124/p/12717369.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_limiting_memory_usage.html
聚合使用一个叫 doc values 的数据结构(在 Doc Values 介绍 里简单介绍)。 Doc values 可以使聚合更快、更高效并且内存友好,所以理解它的工作方式十分有益。
Doc values 的存在是因为倒排索引只对某些操作是高效的。 倒排索引的优势 在于查找包含某个项的文档,而对于从另外一个方向的相反操作并不高效,即:确定哪些项是否存在单个文档里,聚合需要这种次级的访问模式。
在 Elasticsearch
中,Doc Values
就是一种列式存储结构。
Doc Values
是在索引时与 倒排索引
同时生成。也就是说 Doc Values
和 倒排索引
一样,基于 Segement
生成并且是不可变的。同时 Doc Values
和 倒排索引
一样序列化到磁盘,这样对性能和扩展性有很大帮助。
因此我们可以通过禁用某些字段的doc_values来节约磁盘空间。
我们对字符串做聚合时,如何时默认的,那么久会出现很坑的点。demo如下:
1 | POST /agg_analysis/data/_bulk |
我们对state聚合,结果出现了很多奇怪的桶,这个就是对string这个字段每个词都做了桶,为了禁止这个可以直接加keyword,来处理。
1 | GET /data1/data1/_search |
对于常规字段和text字段的keyword,都是直接走doc values的,这个列索引。效率很高,如果需要对这个字段进行桶聚合,那么就要开启fielddata 。
analyzed字符串的字段,字段分词后占用空间很大,正排索引不能很有效的表示多值字符串,所以正排索引不支持此类字段。
fielddata结构与正排索引类似,是另外一份数据,构建和管理100%在内存中,并常驻于JVM内存堆,极易引起OOM问题。
Fielddata 是 延迟 加载。如果你从来没有聚合一个分析字符串,就不会加载 fielddata 到内存中。此外,fielddata 是基于字段加载的, 这意味着只有很活跃地使用字段才会增加 fielddata 的负担。
实际情况是,fielddata 会加载索引中(针对该特定字段的) 所有的 文档,而不管查询的特异性。逻辑是这样:如果查询会访问文档 X、Y 和 Z,那很有可能会在下一个查询中访问其他文档。因此会把所有的文档全部加载进来。
与 doc values 不同,fielddata 结构不会在索引时创建。相反,它是在查询运行时,动态填充。这可能是一个比较复杂的操作,可能需要一些时间。 将所有的信息一次加载,再将其维持在内存中的方式要比反复只加载一个 fielddata 的部分代价要低。
因此很少使用。
由于fielddata 使用的是jvm堆内存,因此这里扩展下elasticSearch内存分配以及jvm内存。
这里参考另外一篇文档ElasticSearch 内存配置及其原因里面详细描述了elasticSearch的分配策略以及原理。
indices.fielddata.cache.size
控制为 fielddata 分配的堆空间大小。 当你发起一个查询,如果这些字符串之前没有被加载过,分析字符串的聚合将会被加载到 fielddata,加载过,直接取用,前面说过了,这个是全局加载。
默认情况下,设置都是 unbounded ,Elasticsearch 永远都不会从 fielddata 中回收数据。
设想我们正在对日志进行索引,每天使用一个新的索引。通常我们只对过去一两天的数据感兴趣,尽管我们会保留老的索引,但我们很少需要查询它们。不过如果采用默认设置,旧索引的 fielddata 永远不会从缓存中回收! fieldata 会保持增长直到 fielddata 发生断熔(请参阅 断路器),这样我们就无法载入更多的 fielddata。
为了防止发生这样的事情,可以通过在 config/elasticsearch.yml
文件中增加配置为 fielddata 设置一个上限:
1 | indices.fielddata.cache.size: 20% |
有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间。